// Scintilla source code edit control
/** @file ScintillaWin.cxx
 ** Windows specific subclass of ScintillaBase.
 **/
// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#include <new>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <limits.h>

#include <string>
#include <vector>

#undef _WIN32_WINNT
#define _WIN32_WINNT  0x0500
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include <windowsx.h>

#include "Platform.h"

#include "ILexer.h"
#include "Scintilla.h"

#ifdef SCI_LEXER
#include "SciLexer.h"
#include "LexerModule.h"
#endif
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "ContractionState.h"
#include "CellBuffer.h"
#include "CallTip.h"
#include "KeyMap.h"
#include "Indicator.h"
#include "XPM.h"
#include "LineMarker.h"
#include "Style.h"
#include "AutoComplete.h"
#include "ViewStyle.h"
#include "CharClassify.h"
#include "Decoration.h"
#include "Document.h"
#include "Selection.h"
#include "PositionCache.h"
#include "Editor.h"
#include "ScintillaBase.h"
#include "UniConversion.h"

#ifdef SCI_LEXER
#include "ExternalLexer.h"
#endif

#ifndef SPI_GETWHEELSCROLLLINES
#define SPI_GETWHEELSCROLLLINES   104
#endif

#ifndef WM_UNICHAR
#define WM_UNICHAR                      0x0109
#endif

#ifndef UNICODE_NOCHAR
#define UNICODE_NOCHAR                  0xFFFF
#endif

#ifndef WM_IME_STARTCOMPOSITION
#include <imm.h>
#endif

#include <commctrl.h>
#ifndef __DMC__
#include <zmouse.h>
#endif
#include <ole2.h>

#ifndef MK_ALT
#define MK_ALT 32
#endif

#define SC_WIN_IDLE 5001

// Functions imported from PlatWin
extern bool IsNT();
extern void Platform_Initialise(void *hInstance);
extern void Platform_Finalise();

typedef BOOL (WINAPI *TrackMouseEventSig)(LPTRACKMOUSEEVENT);

// GCC has trouble with the standard COM ABI so do it the old C way with explicit vtables.

const TCHAR scintillaClassName[] = TEXT("Scintilla");
const TCHAR callClassName[] = TEXT("CallTip");

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif

class ScintillaWin;     // Forward declaration for COM interface subobjects

typedef void VFunction(void);

/**
 */
class FormatEnumerator {
public:
    VFunction **vtbl;
    int ref;
    int pos;
    CLIPFORMAT formats[2];
    int formatsLen;
    FormatEnumerator(int pos_, CLIPFORMAT formats_[], int formatsLen_);
};

/**
 */
class DropSource {
public:
    VFunction **vtbl;
    ScintillaWin *sci;
    DropSource();
};

/**
 */
class DataObject {
public:
    VFunction **vtbl;
    ScintillaWin *sci;
    DataObject();
};

/**
 */
class DropTarget {
public:
    VFunction **vtbl;
    ScintillaWin *sci;
    DropTarget();
};

/**
 */
class ScintillaWin :
    public ScintillaBase {

    bool lastKeyDownConsumed;

    bool capturedMouse;
    bool trackedMouseLeave;
    TrackMouseEventSig TrackMouseEventFn;

    unsigned int linesPerScroll;    ///< Intellimouse support
    int wheelDelta; ///< Wheel delta from roll

    HRGN hRgnUpdate;

    bool hasOKText;

    CLIPFORMAT cfColumnSelect;
    CLIPFORMAT cfLineSelect;

    HRESULT hrOle;
    DropSource ds;
    DataObject dob;
    DropTarget dt;

    static HINSTANCE hInstance;

    ScintillaWin(HWND hwnd);
    ScintillaWin(const ScintillaWin &);
    virtual ~ScintillaWin();
    ScintillaWin &operator=(const ScintillaWin &);

    virtual void Initialise();
    virtual void Finalise();
    HWND MainHWND();

    static sptr_t DirectFunction(
            ScintillaWin *sci, UINT iMessage, uptr_t wParam, sptr_t lParam);
    static sptr_t PASCAL SWndProc(
            HWND hWnd, UINT iMessage, WPARAM wParam, sptr_t lParam);
    static sptr_t PASCAL CTWndProc(
            HWND hWnd, UINT iMessage, WPARAM wParam, sptr_t lParam);

    enum { invalidTimerID, standardTimerID, idleTimerID };

    virtual bool DragThreshold(Point ptStart, Point ptNow);
    virtual void StartDrag();
    sptr_t WndPaint(uptr_t wParam);
    sptr_t HandleComposition(uptr_t wParam, sptr_t lParam);
    UINT CodePageOfDocument();
    virtual bool ValidCodePage(int codePage) const;
    virtual sptr_t DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
    virtual bool SetIdle(bool on);
    virtual void SetTicking(bool on);
    virtual void SetMouseCapture(bool on);
    virtual bool HaveMouseCapture();
    virtual void SetTrackMouseLeaveEvent(bool on);
    virtual bool PaintContains(PRectangle rc);
    virtual void ScrollText(int linesToMove);
    virtual void UpdateSystemCaret();
    virtual void SetVerticalScrollPos();
    virtual void SetHorizontalScrollPos();
    virtual bool ModifyScrollBars(int nMax, int nPage);
    virtual void NotifyChange();
    virtual void NotifyFocus(bool focus);
    virtual int GetCtrlID();
    virtual void NotifyParent(SCNotification scn);
    virtual void NotifyDoubleClick(Point pt, bool shift, bool ctrl, bool alt);
    virtual CaseFolder *CaseFolderForEncoding();
    virtual std::string CaseMapString(const std::string &s, int caseMapping);
    virtual void Copy();
    virtual void CopyAllowLine();
    virtual bool CanPaste();
    virtual void Paste();
    virtual void CreateCallTipWindow(PRectangle rc);
    virtual void AddToPopUp(const char *label, int cmd = 0, bool enabled = true);
    virtual void ClaimSelection();

    // DBCS
    void ImeStartComposition();
    void ImeEndComposition();

    void AddCharBytes(char b0, char b1);

    void GetIntelliMouseParameters();
    virtual void CopyToClipboard(const SelectionText &selectedText);
    void ScrollMessage(WPARAM wParam);
    void HorizontalScrollMessage(WPARAM wParam);
    void RealizeWindowPalette(bool inBackGround);
    void FullPaint();
    void FullPaintDC(HDC dc);
    bool IsCompatibleDC(HDC dc);
    DWORD EffectFromState(DWORD grfKeyState);

    virtual int SetScrollInfo(int nBar, LPCSCROLLINFO lpsi, BOOL bRedraw);
    virtual bool GetScrollInfo(int nBar, LPSCROLLINFO lpsi);
    void ChangeScrollPos(int barType, int pos);

    void InsertPasteText(const char *text, int len, SelectionPosition selStart, bool isRectangular, bool isLine);

public:
    // Public for benefit of Scintilla_DirectFunction
    virtual sptr_t WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam);

    /// Implement IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv);
    STDMETHODIMP_(ULONG)AddRef();
    STDMETHODIMP_(ULONG)Release();

    /// Implement IDropTarget
    STDMETHODIMP DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
                           POINTL pt, PDWORD pdwEffect);
    STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect);
    STDMETHODIMP DragLeave();
    STDMETHODIMP Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
                      POINTL pt, PDWORD pdwEffect);

    /// Implement important part of IDataObject
    STDMETHODIMP GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM);

    static bool Register(HINSTANCE hInstance_);
    static bool Unregister();

    friend class DropSource;
    friend class DataObject;
    friend class DropTarget;
    bool DragIsRectangularOK(CLIPFORMAT fmt) {
        return drag.rectangular && (fmt == cfColumnSelect);
    }

private:
    // For use in creating a system caret
    bool HasCaretSizeChanged();
    BOOL CreateSystemCaret();
    BOOL DestroySystemCaret();
    HBITMAP sysCaretBitmap;
    int sysCaretWidth;
    int sysCaretHeight;
    bool keysAlwaysUnicode;
};

HINSTANCE ScintillaWin::hInstance = 0;

ScintillaWin::ScintillaWin(HWND hwnd) {

    lastKeyDownConsumed = false;

    capturedMouse = false;
    trackedMouseLeave = false;
    TrackMouseEventFn = 0;

    linesPerScroll = 0;
    wheelDelta = 0;   // Wheel delta from roll

    hRgnUpdate = 0;

    hasOKText = false;

    // There does not seem to be a real standard for indicating that the clipboard
    // contains a rectangular selection, so copy Developer Studio.
    cfColumnSelect = static_cast<CLIPFORMAT>(
        ::RegisterClipboardFormat(TEXT("MSDEVColumnSelect")));

    // Likewise for line-copy (copies a full line when no text is selected)
    cfLineSelect = static_cast<CLIPFORMAT>(
        ::RegisterClipboardFormat(TEXT("MSDEVLineSelect")));

    hrOle = E_FAIL;

    wMain = hwnd;

    dob.sci = this;
    ds.sci = this;
    dt.sci = this;

    sysCaretBitmap = 0;
    sysCaretWidth = 0;
    sysCaretHeight = 0;

    keysAlwaysUnicode = false;

    caret.period = ::GetCaretBlinkTime();
    if (caret.period < 0)
        caret.period = 0;

    Initialise();
}

ScintillaWin::~ScintillaWin() {}

void ScintillaWin::Initialise() {
    // Initialize COM.  If the app has already done this it will have
    // no effect.  If the app hasnt, we really shouldnt ask them to call
    // it just so this internal feature works.
    hrOle = ::OleInitialize(NULL);

    // Find TrackMouseEvent which is available on Windows > 95
    HMODULE user32 = ::GetModuleHandle(TEXT("user32.dll"));
    TrackMouseEventFn = (TrackMouseEventSig)::GetProcAddress(user32, "TrackMouseEvent");
    if (TrackMouseEventFn == NULL) {
        // Windows 95 has an emulation in comctl32.dll:_TrackMouseEvent
        HMODULE commctrl32 = ::LoadLibrary(TEXT("comctl32.dll"));
        if (commctrl32 != NULL) {
            TrackMouseEventFn = (TrackMouseEventSig)
                ::GetProcAddress(commctrl32, "_TrackMouseEvent");
        }
    }
}

void ScintillaWin::Finalise() {
    ScintillaBase::Finalise();
    SetTicking(false);
    SetIdle(false);
    ::RevokeDragDrop(MainHWND());
    if (SUCCEEDED(hrOle)) {
        ::OleUninitialize();
    }
}

HWND ScintillaWin::MainHWND() {
    return reinterpret_cast<HWND>(wMain.GetID());
}

bool ScintillaWin::DragThreshold(Point ptStart, Point ptNow) {
    int xMove = abs(ptStart.x - ptNow.x);
    int yMove = abs(ptStart.y - ptNow.y);
    return (xMove > ::GetSystemMetrics(SM_CXDRAG)) ||
        (yMove > ::GetSystemMetrics(SM_CYDRAG));
}

void ScintillaWin::StartDrag() {
    inDragDrop = ddDragging;
    DWORD dwEffect = 0;
    dropWentOutside = true;
    IDataObject *pDataObject = reinterpret_cast<IDataObject *>(&dob);
    IDropSource *pDropSource = reinterpret_cast<IDropSource *>(&ds);
    //Platform::DebugPrintf("About to DoDragDrop %x %x\n", pDataObject, pDropSource);
    HRESULT hr = ::DoDragDrop(
                     pDataObject,
                     pDropSource,
                     DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);
    //Platform::DebugPrintf("DoDragDrop = %x\n", hr);
    if (SUCCEEDED(hr)) {
        if ((hr == DRAGDROP_S_DROP) && (dwEffect == DROPEFFECT_MOVE) && dropWentOutside) {
            // Remove dragged out text
            ClearSelection();
        }
    }
    inDragDrop = ddNone;
    SetDragPosition(SelectionPosition(invalidPosition));
}

// Avoid warnings everywhere for old style casts by concentrating them here
static WORD LoWord(DWORD l) {
    return LOWORD(l);
}

static WORD HiWord(DWORD l) {
    return HIWORD(l);
}

static int InputCodePage() {
    HKL inputLocale = ::GetKeyboardLayout(0);
    LANGID inputLang = LOWORD(inputLocale);
    char sCodePage[10];
    int res = ::GetLocaleInfoA(MAKELCID(inputLang, SORT_DEFAULT),
      LOCALE_IDEFAULTANSICODEPAGE, sCodePage, sizeof(sCodePage));
    if (!res)
        return 0;
    return atoi(sCodePage);
}

#ifndef VK_OEM_2
static const int VK_OEM_2=0xbf;
static const int VK_OEM_3=0xc0;
static const int VK_OEM_4=0xdb;
static const int VK_OEM_5=0xdc;
static const int VK_OEM_6=0xdd;
#endif

/** Map the key codes to their equivalent SCK_ form. */
static int KeyTranslate(int keyIn) {
//PLATFORM_ASSERT(!keyIn);
    switch (keyIn) {
        case VK_DOWN:       return SCK_DOWN;
        case VK_UP:     return SCK_UP;
        case VK_LEFT:       return SCK_LEFT;
        case VK_RIGHT:      return SCK_RIGHT;
        case VK_HOME:       return SCK_HOME;
        case VK_END:        return SCK_END;
        case VK_PRIOR:      return SCK_PRIOR;
        case VK_NEXT:       return SCK_NEXT;
        case VK_DELETE: return SCK_DELETE;
        case VK_INSERT:     return SCK_INSERT;
        case VK_ESCAPE: return SCK_ESCAPE;
        case VK_BACK:       return SCK_BACK;
        case VK_TAB:        return SCK_TAB;
        case VK_RETURN: return SCK_RETURN;
        case VK_ADD:        return SCK_ADD;
        case VK_SUBTRACT:   return SCK_SUBTRACT;
        case VK_DIVIDE:     return SCK_DIVIDE;
        case VK_LWIN:       return SCK_WIN;
        case VK_RWIN:       return SCK_RWIN;
        case VK_APPS:       return SCK_MENU;
        case VK_OEM_2:      return '/';
        case VK_OEM_3:      return '`';
        case VK_OEM_4:      return '[';
        case VK_OEM_5:      return '\\';
        case VK_OEM_6:      return ']';
        default:            return keyIn;
    }
}

LRESULT ScintillaWin::WndPaint(uptr_t wParam) {
    //ElapsedTime et;

    // Redirect assertions to debug output and save current state
    bool assertsPopup = Platform::ShowAssertionPopUps(false);
    paintState = painting;
    PAINTSTRUCT ps;
    PAINTSTRUCT *pps;

    bool IsOcxCtrl = (wParam != 0); // if wParam != 0, it contains
                                   // a PAINSTRUCT* from the OCX
    // Removed since this interferes with reporting other assertions as it occurs repeatedly
    //PLATFORM_ASSERT(hRgnUpdate == NULL);
    hRgnUpdate = ::CreateRectRgn(0, 0, 0, 0);
    if (IsOcxCtrl) {
        pps = reinterpret_cast<PAINTSTRUCT*>(wParam);
    } else {
        ::GetUpdateRgn(MainHWND(), hRgnUpdate, FALSE);
        pps = &ps;
        ::BeginPaint(MainHWND(), pps);
    }
    AutoSurface surfaceWindow(pps->hdc, this);
    if (surfaceWindow) {
        rcPaint = PRectangle(pps->rcPaint.left, pps->rcPaint.top, pps->rcPaint.right, pps->rcPaint.bottom);
        PRectangle rcClient = GetClientRectangle();
        paintingAllText = rcPaint.Contains(rcClient);
        if (paintingAllText) {
            //Platform::DebugPrintf("Performing full text paint\n");
        } else {
            //Platform::DebugPrintf("Performing partial paint %d .. %d\n", rcPaint.top, rcPaint.bottom);
        }
        Paint(surfaceWindow, rcPaint);
        surfaceWindow->Release();
    }
    if (hRgnUpdate) {
        ::DeleteRgn(hRgnUpdate);
        hRgnUpdate = 0;
    }

    if (!IsOcxCtrl)
        ::EndPaint(MainHWND(), pps);
    if (paintState == paintAbandoned) {
        // Painting area was insufficient to cover new styling or brace highlight positions
        FullPaint();
    }
    paintState = notPainting;

    // Restore debug output state
    Platform::ShowAssertionPopUps(assertsPopup);

    //Platform::DebugPrintf("Paint took %g\n", et.Duration());
    return 0l;
}

sptr_t ScintillaWin::HandleComposition(uptr_t wParam, sptr_t lParam) {
#ifdef __DMC__
    // Digital Mars compiler does not include Imm library
    return 0;
#else
    if (lParam & GCS_RESULTSTR) {
        HIMC hIMC = ::ImmGetContext(MainHWND());
        if (hIMC) {
            const int maxLenInputIME = 200;
            wchar_t wcs[maxLenInputIME];
            LONG bytes = ::ImmGetCompositionStringW(hIMC,
                GCS_RESULTSTR, wcs, (maxLenInputIME-1)*2);
            int wides = bytes / 2;
            if (IsUnicodeMode()) {
                char utfval[maxLenInputIME * 3];
                unsigned int len = UTF8Length(wcs, wides);
                UTF8FromUTF16(wcs, wides, utfval, len);
                utfval[len] = '\0';
                AddCharUTF(utfval, len);
            } else {
                char dbcsval[maxLenInputIME * 2];
                int size = ::WideCharToMultiByte(InputCodePage(),
                    0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
                for (int i=0; i<size; i++) {
                    AddChar(dbcsval[i]);
                }
            }
            // Set new position after converted
            Point pos = PointMainCaret();
            COMPOSITIONFORM CompForm;
            CompForm.dwStyle = CFS_POINT;
            CompForm.ptCurrentPos.x = pos.x;
            CompForm.ptCurrentPos.y = pos.y;
            ::ImmSetCompositionWindow(hIMC, &CompForm);
            ::ImmReleaseContext(MainHWND(), hIMC);
        }
        return 0;
    }
    return ::DefWindowProc(MainHWND(), WM_IME_COMPOSITION, wParam, lParam);
#endif
}

// Translate message IDs from WM_* and EM_* to SCI_* so can partly emulate Windows Edit control
static unsigned int SciMessageFromEM(unsigned int iMessage) {
    switch (iMessage) {
    case EM_CANPASTE: return SCI_CANPASTE;
    case EM_CANUNDO: return SCI_CANUNDO;
    case EM_EMPTYUNDOBUFFER: return SCI_EMPTYUNDOBUFFER;
    case EM_FINDTEXTEX: return SCI_FINDTEXT;
    case EM_FORMATRANGE: return SCI_FORMATRANGE;
    case EM_GETFIRSTVISIBLELINE: return SCI_GETFIRSTVISIBLELINE;
    case EM_GETLINECOUNT: return SCI_GETLINECOUNT;
    case EM_GETSELTEXT: return SCI_GETSELTEXT;
    case EM_GETTEXTRANGE: return SCI_GETTEXTRANGE;
    case EM_HIDESELECTION: return SCI_HIDESELECTION;
    case EM_LINEINDEX: return SCI_POSITIONFROMLINE;
    case EM_LINESCROLL: return SCI_LINESCROLL;
    case EM_REPLACESEL: return SCI_REPLACESEL;
    case EM_SCROLLCARET: return SCI_SCROLLCARET;
    case EM_SETREADONLY: return SCI_SETREADONLY;
    case WM_CLEAR: return SCI_CLEAR;
    case WM_COPY: return SCI_COPY;
    case WM_CUT: return SCI_CUT;
    case WM_GETTEXT: return SCI_GETTEXT;
    case WM_SETTEXT: return SCI_SETTEXT;
    case WM_GETTEXTLENGTH: return SCI_GETTEXTLENGTH;
    case WM_PASTE: return SCI_PASTE;
    case WM_UNDO: return SCI_UNDO;
    }
    return iMessage;
}

static UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage) {
    if (documentCodePage == SC_CP_UTF8) {
        // The system calls here are a little slow so avoid if known case.
        return SC_CP_UTF8;
    }
    CHARSETINFO ci = { 0, 0, { { 0, 0, 0, 0 }, { 0, 0 } } };
    BOOL bci = ::TranslateCharsetInfo((DWORD*)characterSet,
        &ci, TCI_SRCCHARSET);

    UINT cp;
    if (bci)
        cp = ci.ciACP;
    else
        cp = documentCodePage;

    CPINFO cpi;
    if (!IsValidCodePage(cp) && !GetCPInfo(cp, &cpi))
        cp = CP_ACP;

    return cp;
}

UINT ScintillaWin::CodePageOfDocument() {
    return CodePageFromCharSet(vs.styles[STYLE_DEFAULT].characterSet, pdoc->dbcsCodePage);
}

sptr_t ScintillaWin::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
    try {
        //Platform::DebugPrintf("S M:%x WP:%x L:%x\n", iMessage, wParam, lParam);
        iMessage = SciMessageFromEM(iMessage);
        switch (iMessage) {

        case WM_CREATE:
            ctrlID = ::GetDlgCtrlID(reinterpret_cast<HWND>(wMain.GetID()));
            // Get Intellimouse scroll line parameters
            GetIntelliMouseParameters();
            ::RegisterDragDrop(MainHWND(), reinterpret_cast<IDropTarget *>(&dt));
            break;

        case WM_COMMAND:
            Command(LoWord(wParam));
            break;

        case WM_PAINT:
            return WndPaint(wParam);

        case WM_PRINTCLIENT: {
                HDC hdc = reinterpret_cast<HDC>(wParam);
                if (!IsCompatibleDC(hdc)) {
                    return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
                }
                FullPaintDC(hdc);
            }
            break;

        case WM_VSCROLL:
            ScrollMessage(wParam);
            break;

        case WM_HSCROLL:
            HorizontalScrollMessage(wParam);
            break;

        case WM_SIZE: {
                //Platform::DebugPrintf("Scintilla WM_SIZE %d %d\n", LoWord(lParam), HiWord(lParam));
                ChangeSize();
            }
            break;

        case WM_MOUSEWHEEL:
            // Don't handle datazoom.
            // (A good idea for datazoom would be to "fold" or "unfold" details.
            // i.e. if datazoomed out only class structures are visible, when datazooming in the control
            // structures appear, then eventually the individual statements...)
            if (wParam & MK_SHIFT) {
                return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
            }

            // Either SCROLL or ZOOM. We handle the wheel steppings calculation
            wheelDelta -= static_cast<short>(HiWord(wParam));
            if (abs(wheelDelta) >= WHEEL_DELTA && linesPerScroll > 0) {
                int linesToScroll = linesPerScroll;
                if (linesPerScroll == WHEEL_PAGESCROLL)
                    linesToScroll = LinesOnScreen() - 1;
                if (linesToScroll == 0) {
                    linesToScroll = 1;
                }
                linesToScroll *= (wheelDelta / WHEEL_DELTA);
                if (wheelDelta >= 0)
                    wheelDelta = wheelDelta % WHEEL_DELTA;
                else
                    wheelDelta = - (-wheelDelta % WHEEL_DELTA);

                if (wParam & MK_CONTROL) {
                    // Zoom! We play with the font sizes in the styles.
                    // Number of steps/line is ignored, we just care if sizing up or down
                    if (linesToScroll < 0) {
                        KeyCommand(SCI_ZOOMIN);
                    } else {
                        KeyCommand(SCI_ZOOMOUT);
                    }
                } else {
                    // Scroll
                    ScrollTo(topLine + linesToScroll);
                }
            }
            return 0;

        case WM_TIMER:
            if (wParam == standardTimerID && timer.ticking) {
                Tick();
            } else if (wParam == idleTimerID && idler.state) {
                SendMessage(MainHWND(), SC_WIN_IDLE, 0, 1);
            } else {
                return 1;
            }
            break;

        case SC_WIN_IDLE:
            // wParam=dwTickCountInitial, or 0 to initialize.  lParam=bSkipUserInputTest
            if (idler.state) {
                if (lParam || (WAIT_TIMEOUT == MsgWaitForMultipleObjects(0, 0, 0, 0, QS_INPUT|QS_HOTKEY))) {
                    if (Idle()) {
                        // User input was given priority above, but all events do get a turn.  Other
                        // messages, notifications, etc. will get interleaved with the idle messages.

                        // However, some things like WM_PAINT are a lower priority, and will not fire
                        // when there's a message posted.  So, several times a second, we stop and let
                        // the low priority events have a turn (after which the timer will fire again).

                        DWORD dwCurrent = GetTickCount();
                        DWORD dwStart = wParam ? wParam : dwCurrent;

                        if (dwCurrent >= dwStart && dwCurrent > 200 && dwCurrent - 200 < dwStart)
                            PostMessage(MainHWND(), SC_WIN_IDLE, dwStart, 0);
                    } else {
                        SetIdle(false);
                    }
                }
            }
            break;

        case WM_GETMINMAXINFO:
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_LBUTTONDOWN: {
#ifndef __DMC__
            // Digital Mars compiler does not include Imm library
            // For IME, set the composition string as the result string.
            HIMC hIMC = ::ImmGetContext(MainHWND());
            ::ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
            ::ImmReleaseContext(MainHWND(), hIMC);
#endif
            //
            //Platform::DebugPrintf("Buttdown %d %x %x %x %x %x\n",iMessage, wParam, lParam,
            //  Platform::IsKeyDown(VK_SHIFT),
            //  Platform::IsKeyDown(VK_CONTROL),
            //  Platform::IsKeyDown(VK_MENU));
            ::SetFocus(MainHWND());
            ButtonDown(Point::FromLong(lParam), ::GetMessageTime(),
                (wParam & MK_SHIFT) != 0,
                (wParam & MK_CONTROL) != 0,
                Platform::IsKeyDown(VK_MENU));
            }
            break;

        case WM_MOUSEMOVE:
            SetTrackMouseLeaveEvent(true);
            ButtonMove(Point::FromLong(lParam));
            break;

        case WM_MOUSELEAVE:
            SetTrackMouseLeaveEvent(false);
            MouseLeave();
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_LBUTTONUP:
            ButtonUp(Point::FromLong(lParam),
                ::GetMessageTime(),
                (wParam & MK_CONTROL) != 0);
            break;

        case WM_RBUTTONDOWN:
            ::SetFocus(MainHWND());
            if (!PointInSelection(Point::FromLong(lParam))) {
                CancelModes();
                SetEmptySelection(PositionFromLocation(Point::FromLong(lParam)));
            }
            break;

        case WM_SETCURSOR:
            if (LoWord(lParam) == HTCLIENT) {
                if (inDragDrop == ddDragging) {
                    DisplayCursor(Window::cursorUp);
                } else {
                    // Display regular (drag) cursor over selection
                    POINT pt;
                    ::GetCursorPos(&pt);
                    ::ScreenToClient(MainHWND(), &pt);
                    if (PointInSelMargin(Point(pt.x, pt.y))) {
                        DisplayCursor(GetMarginCursor(Point(pt.x, pt.y)));
                    } else if (PointInSelection(Point(pt.x, pt.y)) && !SelectionEmpty()) {
                        DisplayCursor(Window::cursorArrow);
                    } else if (PointIsHotspot(Point(pt.x, pt.y))) {
                        DisplayCursor(Window::cursorHand);
                    } else {
                        DisplayCursor(Window::cursorText);
                    }
                }
                return TRUE;
            } else {
                return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
            }

        case WM_CHAR:
            if (((wParam >= 128) || !iscntrl(wParam)) || !lastKeyDownConsumed) {
                if (::IsWindowUnicode(MainHWND()) || keysAlwaysUnicode) {
                    wchar_t wcs[2] = {wParam, 0};
                    if (IsUnicodeMode()) {
                        // For a wide character version of the window:
                        char utfval[4];
                        unsigned int len = UTF8Length(wcs, 1);
                        UTF8FromUTF16(wcs, 1, utfval, len);
                        AddCharUTF(utfval, len);
                    } else {
                        UINT cpDest = CodePageOfDocument();
                        char inBufferCP[20];
                        int size = ::WideCharToMultiByte(cpDest,
                            0, wcs, 1, inBufferCP, sizeof(inBufferCP) - 1, 0, 0);
                        inBufferCP[size] = '\0';
                        AddCharUTF(inBufferCP, size);
                    }
                } else {
                    if (IsUnicodeMode()) {
                        AddCharBytes('\0', LOBYTE(wParam));
                    } else {
                        AddChar(LOBYTE(wParam));
                    }
                }
            }
            return 0;

        case WM_UNICHAR:
            if (wParam == UNICODE_NOCHAR) {
                return IsUnicodeMode() ? 1 : 0;
            } else if (lastKeyDownConsumed) {
                return 1;
            } else {
                if (IsUnicodeMode()) {
                    char utfval[4];
                    wchar_t wcs[2] = {static_cast<wchar_t>(wParam), 0};
                    unsigned int len = UTF8Length(wcs, 1);
                    UTF8FromUTF16(wcs, 1, utfval, len);
                    AddCharUTF(utfval, len);
                    return 1;
                } else {
                    return 0;
                }
            }

        case WM_SYSKEYDOWN:
        case WM_KEYDOWN: {
            //Platform::DebugPrintf("S keydown %d %x %x %x %x\n",iMessage, wParam, lParam, ::IsKeyDown(VK_SHIFT), ::IsKeyDown(VK_CONTROL));
                lastKeyDownConsumed = false;
                int ret = KeyDown(KeyTranslate(wParam),
                    Platform::IsKeyDown(VK_SHIFT),
                    Platform::IsKeyDown(VK_CONTROL),
                    Platform::IsKeyDown(VK_MENU),
                    &lastKeyDownConsumed);
                if (!ret && !lastKeyDownConsumed) {
                    return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
                }
                break;
            }

        case WM_IME_KEYDOWN:
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_KEYUP:
            //Platform::DebugPrintf("S keyup %d %x %x\n",iMessage, wParam, lParam);
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_SETTINGCHANGE:
            //Platform::DebugPrintf("Setting Changed\n");
            InvalidateStyleData();
            // Get Intellimouse scroll line parameters
            GetIntelliMouseParameters();
            break;

        case WM_GETDLGCODE:
            return DLGC_HASSETSEL | DLGC_WANTALLKEYS;

        case WM_KILLFOCUS: {
                HWND wOther = reinterpret_cast<HWND>(wParam);
                HWND wThis = MainHWND();
                HWND wCT = reinterpret_cast<HWND>(ct.wCallTip.GetID());
                if (!wParam ||
                    !(::IsChild(wThis, wOther) || (wOther == wCT))) {
                    SetFocusState(false);
                    DestroySystemCaret();
                }
            }
            //RealizeWindowPalette(true);
            break;

        case WM_SETFOCUS:
            SetFocusState(true);
            RealizeWindowPalette(false);
            DestroySystemCaret();
            CreateSystemCaret();
            break;

        case WM_SYSCOLORCHANGE:
            //Platform::DebugPrintf("Setting Changed\n");
            InvalidateStyleData();
            break;

        case WM_PALETTECHANGED:
            if (wParam != reinterpret_cast<uptr_t>(MainHWND())) {
                //Platform::DebugPrintf("** Palette Changed\n");
                RealizeWindowPalette(true);
            }
            break;

        case WM_QUERYNEWPALETTE:
            //Platform::DebugPrintf("** Query palette\n");
            RealizeWindowPalette(false);
            break;

        case WM_IME_STARTCOMPOSITION:   // dbcs
            ImeStartComposition();
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_IME_ENDCOMPOSITION:     // dbcs
            ImeEndComposition();
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_IME_COMPOSITION:
            return HandleComposition(wParam, lParam);

        case WM_IME_CHAR: {
                AddCharBytes(HIBYTE(wParam), LOBYTE(wParam));
                return 0;
            }

        case WM_CONTEXTMENU:
            if (displayPopupMenu) {
                Point pt = Point::FromLong(lParam);
                if ((pt.x == -1) && (pt.y == -1)) {
                    // Caused by keyboard so display menu near caret
                    pt = PointMainCaret();
                    POINT spt = {pt.x, pt.y};
                    ::ClientToScreen(MainHWND(), &spt);
                    pt = Point(spt.x, spt.y);
                }
                ContextMenu(pt);
                return 0;
            }
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_INPUTLANGCHANGE:
            //::SetThreadLocale(LOWORD(lParam));
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_INPUTLANGCHANGEREQUEST:
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case WM_ERASEBKGND:
            return 1;   // Avoid any background erasure as whole window painted.

        case WM_CAPTURECHANGED:
            capturedMouse = false;
            return 0;

        // These are not handled in Scintilla and its faster to dispatch them here.
        // Also moves time out to here so profile doesn't count lots of empty message calls.

        case WM_MOVE:
        case WM_MOUSEACTIVATE:
        case WM_NCHITTEST:
        case WM_NCCALCSIZE:
        case WM_NCPAINT:
        case WM_NCMOUSEMOVE:
        case WM_NCLBUTTONDOWN:
        case WM_IME_SETCONTEXT:
        case WM_IME_NOTIFY:
        case WM_SYSCOMMAND:
        case WM_WINDOWPOSCHANGING:
        case WM_WINDOWPOSCHANGED:
            return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);

        case EM_LINEFROMCHAR:
            if (static_cast<int>(wParam) < 0) {
                wParam = SelectionStart().Position();
            }
            return pdoc->LineFromPosition(wParam);

        case EM_EXLINEFROMCHAR:
            return pdoc->LineFromPosition(lParam);

        case EM_GETSEL:
            if (wParam) {
                *reinterpret_cast<int *>(wParam) = SelectionStart().Position();
            }
            if (lParam) {
                *reinterpret_cast<int *>(lParam) = SelectionEnd().Position();
            }
            return MAKELONG(SelectionStart().Position(), SelectionEnd().Position());

        case EM_EXGETSEL: {
                if (lParam == 0) {
                    return 0;
                }
                Sci_CharacterRange *pCR = reinterpret_cast<Sci_CharacterRange *>(lParam);
                pCR->cpMin = SelectionStart().Position();
                pCR->cpMax = SelectionEnd().Position();
            }
            break;

        case EM_SETSEL: {
                int nStart = static_cast<int>(wParam);
                int nEnd = static_cast<int>(lParam);
                if (nStart == 0 && nEnd == -1) {
                    nEnd = pdoc->Length();
                }
                if (nStart == -1) {
                    nStart = nEnd;  // Remove selection
                }
                if (nStart > nEnd) {
                    SetSelection(nEnd, nStart);
                } else {
                    SetSelection(nStart, nEnd);
                }
                EnsureCaretVisible();
            }
            break;

        case EM_EXSETSEL: {
                if (lParam == 0) {
                    return 0;
                }
                Sci_CharacterRange *pCR = reinterpret_cast<Sci_CharacterRange *>(lParam);
                sel.selType = Selection::selStream;
                if (pCR->cpMin == 0 && pCR->cpMax == -1) {
                    SetSelection(pCR->cpMin, pdoc->Length());
                } else {
                    SetSelection(pCR->cpMin, pCR->cpMax);
                }
                EnsureCaretVisible();
                return pdoc->LineFromPosition(SelectionStart().Position());
            }

        case SCI_GETDIRECTFUNCTION:
            return reinterpret_cast<sptr_t>(DirectFunction);

        case SCI_GETDIRECTPOINTER:
            return reinterpret_cast<sptr_t>(this);

        case SCI_GRABFOCUS:
            ::SetFocus(MainHWND());
            break;

        case SCI_SETKEYSUNICODE:
            keysAlwaysUnicode = wParam != 0;
            break;

        case SCI_GETKEYSUNICODE:
            return keysAlwaysUnicode;

#ifdef SCI_LEXER
        case SCI_LOADLEXERLIBRARY:
            LexerManager::GetInstance()->Load(reinterpret_cast<const char *>(lParam));
            break;
#endif

        default:
            return ScintillaBase::WndProc(iMessage, wParam, lParam);
        }
    } catch (std::bad_alloc &) {
        errorStatus = SC_STATUS_BADALLOC;
    } catch (...) {
        errorStatus = SC_STATUS_FAILURE;
    }
    return 0l;
}

bool ScintillaWin::ValidCodePage(int codePage) const {
    return codePage == 0 || codePage == SC_CP_UTF8 ||
           codePage == 932 || codePage == 936 || codePage == 949 ||
           codePage == 950 || codePage == 1361;
}

sptr_t ScintillaWin::DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
    return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);
}

void ScintillaWin::SetTicking(bool on) {
    if (timer.ticking != on) {
        timer.ticking = on;
        if (timer.ticking) {
            timer.tickerID = ::SetTimer(MainHWND(), standardTimerID, timer.tickSize, NULL)
                ? reinterpret_cast<TickerID>(standardTimerID) : 0;
        } else {
            ::KillTimer(MainHWND(), reinterpret_cast<uptr_t>(timer.tickerID));
            timer.tickerID = 0;
        }
    }
    timer.ticksToWait = caret.period;
}

bool ScintillaWin::SetIdle(bool on) {
    // On Win32 the Idler is implemented as a Timer on the Scintilla window.  This
    // takes advantage of the fact that WM_TIMER messages are very low priority,
    // and are only posted when the message queue is empty, i.e. during idle time.
    if (idler.state != on) {
        if (on) {
            idler.idlerID = ::SetTimer(MainHWND(), idleTimerID, 10, NULL)
                ? reinterpret_cast<IdlerID>(idleTimerID) : 0;
        } else {
            ::KillTimer(MainHWND(), reinterpret_cast<uptr_t>(idler.idlerID));
            idler.idlerID = 0;
        }
        idler.state = idler.idlerID != 0;
    }
    return idler.state;
}

void ScintillaWin::SetMouseCapture(bool on) {
    if (mouseDownCaptures) {
        if (on) {
            ::SetCapture(MainHWND());
        } else {
            ::ReleaseCapture();
        }
    }
    capturedMouse = on;
}

bool ScintillaWin::HaveMouseCapture() {
    // Cannot just see if GetCapture is this window as the scroll bar also sets capture for the window
    return capturedMouse;
    //return capturedMouse && (::GetCapture() == MainHWND());
}

void ScintillaWin::SetTrackMouseLeaveEvent(bool on) {
    if (on && TrackMouseEventFn && !trackedMouseLeave) {
        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(tme);
        tme.dwFlags = TME_LEAVE;
        tme.hwndTrack = MainHWND();
        TrackMouseEventFn(&tme);
    }
    trackedMouseLeave = on;
}

bool ScintillaWin::PaintContains(PRectangle rc) {
    bool contains = true;
    if ((paintState == painting) && (!rc.Empty())) {
        if (!rcPaint.Contains(rc)) {
            contains = false;
        } else {
            // In bounding rectangle so check more accurately using region
            HRGN hRgnRange = ::CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
            if (hRgnRange) {
                HRGN hRgnDest = ::CreateRectRgn(0, 0, 0, 0);
                if (hRgnDest) {
                    int combination = ::CombineRgn(hRgnDest, hRgnRange, hRgnUpdate, RGN_DIFF);
                    if (combination != NULLREGION) {
                        contains = false;
                    }
                    ::DeleteRgn(hRgnDest);
                }
                ::DeleteRgn(hRgnRange);
            }
        }
    }
    return contains;
}

void ScintillaWin::ScrollText(int linesToMove) {
    //Platform::DebugPrintf("ScintillaWin::ScrollText %d\n", linesToMove);
    ::ScrollWindow(MainHWND(), 0,
        vs.lineHeight * linesToMove, 0, 0);
    ::UpdateWindow(MainHWND());
}

void ScintillaWin::UpdateSystemCaret() {
    if (hasFocus) {
        if (HasCaretSizeChanged()) {
            DestroySystemCaret();
            CreateSystemCaret();
        }
        Point pos = PointMainCaret();
        ::SetCaretPos(pos.x, pos.y);
    }
}

int ScintillaWin::SetScrollInfo(int nBar, LPCSCROLLINFO lpsi, BOOL bRedraw) {
    return ::SetScrollInfo(MainHWND(), nBar, lpsi, bRedraw);
}

bool ScintillaWin::GetScrollInfo(int nBar, LPSCROLLINFO lpsi) {
    return ::GetScrollInfo(MainHWND(), nBar, lpsi) ? true : false;
}

// Change the scroll position but avoid repaint if changing to same value
void ScintillaWin::ChangeScrollPos(int barType, int pos) {
    SCROLLINFO sci = {
        sizeof(sci), 0, 0, 0, 0, 0, 0
    };
    sci.fMask = SIF_POS;
    GetScrollInfo(barType, &sci);
    if (sci.nPos != pos) {
        DwellEnd(true);
        sci.nPos = pos;
        SetScrollInfo(barType, &sci, TRUE);
    }
}

void ScintillaWin::SetVerticalScrollPos() {
    ChangeScrollPos(SB_VERT, topLine);
}

void ScintillaWin::SetHorizontalScrollPos() {
    ChangeScrollPos(SB_HORZ, xOffset);
}

bool ScintillaWin::ModifyScrollBars(int nMax, int nPage) {
    bool modified = false;
    SCROLLINFO sci = {
        sizeof(sci), 0, 0, 0, 0, 0, 0
    };
    sci.fMask = SIF_PAGE | SIF_RANGE;
    GetScrollInfo(SB_VERT, &sci);
    int vertEndPreferred = nMax;
    if (!verticalScrollBarVisible)
        vertEndPreferred = 0;
    if ((sci.nMin != 0) ||
        (sci.nMax != vertEndPreferred) ||
            (sci.nPage != static_cast<unsigned int>(nPage)) ||
            (sci.nPos != 0)) {
        //Platform::DebugPrintf("Scroll info changed %d %d %d %d %d\n",
        //  sci.nMin, sci.nMax, sci.nPage, sci.nPos, sci.nTrackPos);
        sci.fMask = SIF_PAGE | SIF_RANGE;
        sci.nMin = 0;
        sci.nMax = vertEndPreferred;
        sci.nPage = nPage;
        sci.nPos = 0;
        sci.nTrackPos = 1;
        SetScrollInfo(SB_VERT, &sci, TRUE);
        modified = true;
    }

    PRectangle rcText = GetTextRectangle();
    int horizEndPreferred = scrollWidth;
    if (horizEndPreferred < 0)
        horizEndPreferred = 0;
    if (!horizontalScrollBarVisible || (wrapState != eWrapNone))
        horizEndPreferred = 0;
    unsigned int pageWidth = rcText.Width();
    sci.fMask = SIF_PAGE | SIF_RANGE;
    GetScrollInfo(SB_HORZ, &sci);
    if ((sci.nMin != 0) ||
        (sci.nMax != horizEndPreferred) ||
        (sci.nPage != pageWidth) ||
            (sci.nPos != 0)) {
        sci.fMask = SIF_PAGE | SIF_RANGE;
        sci.nMin = 0;
        sci.nMax = horizEndPreferred;
        sci.nPage = pageWidth;
        sci.nPos = 0;
        sci.nTrackPos = 1;
        SetScrollInfo(SB_HORZ, &sci, TRUE);
        modified = true;
        if (scrollWidth < static_cast<int>(pageWidth)) {
            HorizontalScrollTo(0);
        }
    }
    return modified;
}

void ScintillaWin::NotifyChange() {
    ::SendMessage(::GetParent(MainHWND()), WM_COMMAND,
            MAKELONG(GetCtrlID(), SCEN_CHANGE),
        reinterpret_cast<LPARAM>(MainHWND()));
}

void ScintillaWin::NotifyFocus(bool focus) {
    ::SendMessage(::GetParent(MainHWND()), WM_COMMAND,
            MAKELONG(GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS),
        reinterpret_cast<LPARAM>(MainHWND()));
}

int ScintillaWin::GetCtrlID() {
    return ::GetDlgCtrlID(reinterpret_cast<HWND>(wMain.GetID()));
}

void ScintillaWin::NotifyParent(SCNotification scn) {
    scn.nmhdr.hwndFrom = MainHWND();
    scn.nmhdr.idFrom = GetCtrlID();
    ::SendMessage(::GetParent(MainHWND()), WM_NOTIFY,
                  GetCtrlID(), reinterpret_cast<LPARAM>(&scn));
}

void ScintillaWin::NotifyDoubleClick(Point pt, bool shift, bool ctrl, bool alt) {
    //Platform::DebugPrintf("ScintillaWin Double click 0\n");
    ScintillaBase::NotifyDoubleClick(pt, shift, ctrl, alt);
    // Send myself a WM_LBUTTONDBLCLK, so the container can handle it too.
    ::SendMessage(MainHWND(),
              WM_LBUTTONDBLCLK,
              shift ? MK_SHIFT : 0,
              MAKELPARAM(pt.x, pt.y));
}

class CaseFolderUTF8 : public CaseFolderTable {
    // Allocate the expandable storage here so that it does not need to be reallocated
    // for each call to Fold.
    std::vector<wchar_t> utf16Mixed;
    std::vector<wchar_t> utf16Folded;
public:
    CaseFolderUTF8() {
        StandardASCII();
    }
    virtual size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) {
        if ((lenMixed == 1) && (sizeFolded > 0)) {
            folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
            return 1;
        } else {
            if (lenMixed > utf16Mixed.size()) {
                utf16Mixed.resize(lenMixed + 8);
            }
            size_t nUtf16Mixed = ::MultiByteToWideChar(65001, 0, mixed, lenMixed,
                &utf16Mixed[0], utf16Mixed.size());

            if (nUtf16Mixed == 0) {
                // Failed to convert -> bad UTF-8
                folded[0] = '\0';
                return 1;
            }

            if (nUtf16Mixed * 4 > utf16Folded.size()) { // Maximum folding expansion factor of 4
                utf16Folded.resize(nUtf16Mixed * 4 + 8);
            }
            int lenFlat = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT,
                LCMAP_LINGUISTIC_CASING | LCMAP_LOWERCASE,
                &utf16Mixed[0], nUtf16Mixed, &utf16Folded[0], utf16Folded.size());

            size_t lenOut = UTF8Length(&utf16Folded[0], lenFlat);
            if (lenOut < sizeFolded) {
                UTF8FromUTF16(&utf16Folded[0], lenFlat, folded, lenOut);
                return lenOut;
            } else {
                return 0;
            }
        }
    }
};

class CaseFolderDBCS : public CaseFolderTable {
    // Allocate the expandable storage here so that it does not need to be reallocated
    // for each call to Fold.
    std::vector<wchar_t> utf16Mixed;
    std::vector<wchar_t> utf16Folded;
    UINT cp;
public:
    CaseFolderDBCS(UINT cp_) : cp(cp_) {
        StandardASCII();
    }
    virtual size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) {
        if ((lenMixed == 1) && (sizeFolded > 0)) {
            folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
            return 1;
        } else {
            if (lenMixed > utf16Mixed.size()) {
                utf16Mixed.resize(lenMixed + 8);
            }
            size_t nUtf16Mixed = ::MultiByteToWideChar(cp, 0, mixed, lenMixed,
                &utf16Mixed[0], utf16Mixed.size());

            if (nUtf16Mixed == 0) {
                // Failed to convert -> bad input
                folded[0] = '\0';
                return 1;
            }

            if (nUtf16Mixed * 4 > utf16Folded.size()) { // Maximum folding expansion factor of 4
                utf16Folded.resize(nUtf16Mixed * 4 + 8);
            }
            int lenFlat = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT,
                LCMAP_LINGUISTIC_CASING | LCMAP_LOWERCASE,
                &utf16Mixed[0], nUtf16Mixed, &utf16Folded[0], utf16Folded.size());

            size_t lenOut = ::WideCharToMultiByte(cp, 0,
                &utf16Folded[0], lenFlat,
                NULL, 0, NULL, 0);

            if (lenOut < sizeFolded) {
                ::WideCharToMultiByte(cp, 0,
                    &utf16Folded[0], lenFlat,
                    folded, lenOut, NULL, 0);
                return lenOut;
            } else {
                return 0;
            }
        }
    }
};

CaseFolder *ScintillaWin::CaseFolderForEncoding() {
    UINT cpDest = CodePageOfDocument();
    if (cpDest == SC_CP_UTF8) {
        return new CaseFolderUTF8();
    } else {
        if (pdoc->dbcsCodePage == 0) {
            CaseFolderTable *pcf = new CaseFolderTable();
            pcf->StandardASCII();
            // Only for single byte encodings
            UINT cpDoc = CodePageOfDocument();
            for (int i=0x80; i<0x100; i++) {
                char sCharacter[2] = "A";
                sCharacter[0] = static_cast<char>(i);
                wchar_t wCharacter[20];
                unsigned int lengthUTF16 = ::MultiByteToWideChar(cpDoc, 0, sCharacter, 1,
                    wCharacter, sizeof(wCharacter)/sizeof(wCharacter[0]));
                if (lengthUTF16 == 1) {
                    wchar_t wLower[20];
                    int charsConverted = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT,
                        LCMAP_LINGUISTIC_CASING | LCMAP_LOWERCASE,
                        wCharacter, lengthUTF16, wLower, sizeof(wLower)/sizeof(wLower[0]));
                    char sCharacterLowered[20];
                    unsigned int lengthConverted = ::WideCharToMultiByte(cpDoc, 0,
                        wLower, charsConverted,
                        sCharacterLowered, sizeof(sCharacterLowered), NULL, 0);
                    if ((lengthConverted == 1) && (sCharacter[0] != sCharacterLowered[0])) {
                        pcf->SetTranslation(sCharacter[0], sCharacterLowered[0]);
                    }
                }
            }
            return pcf;
        } else {
            return new CaseFolderDBCS(cpDest);
        }
    }
}

std::string ScintillaWin::CaseMapString(const std::string &s, int caseMapping) {
    if (s.size() == 0)
        return std::string();

    if (caseMapping == cmSame)
        return s;

    UINT cpDoc = CodePageOfDocument();

    unsigned int lengthUTF16 = ::MultiByteToWideChar(cpDoc, 0, s.c_str(), s.size(), NULL, 0);
    if (lengthUTF16 == 0)   // Failed to convert
        return s;

    DWORD mapFlags = LCMAP_LINGUISTIC_CASING |
        ((caseMapping == cmUpper) ? LCMAP_UPPERCASE : LCMAP_LOWERCASE);

    // Many conversions performed by search function are short so optimize this case.
    enum { shortSize=20 };

    if (s.size() > shortSize) {
        // Use dynamic allocations for long strings

        // Change text to UTF-16
        std::vector<wchar_t> vwcText(lengthUTF16);
        ::MultiByteToWideChar(cpDoc, 0, s.c_str(), s.size(), &vwcText[0], lengthUTF16);

        // Change case
        int charsConverted = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags,
            &vwcText[0], lengthUTF16, NULL, 0);
        std::vector<wchar_t> vwcConverted(charsConverted);
        ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags,
            &vwcText[0], lengthUTF16, &vwcConverted[0], charsConverted);

        // Change back to document encoding
        unsigned int lengthConverted = ::WideCharToMultiByte(cpDoc, 0,
            &vwcConverted[0], vwcConverted.size(),
            NULL, 0, NULL, 0);
        std::vector<char> vcConverted(lengthConverted);
        ::WideCharToMultiByte(cpDoc, 0,
            &vwcConverted[0], vwcConverted.size(),
            &vcConverted[0], vcConverted.size(), NULL, 0);

        return std::string(&vcConverted[0], vcConverted.size());

    } else {
        // Use static allocations for short strings as much faster
        // A factor of 15 for single character strings

        // Change text to UTF-16
        wchar_t vwcText[shortSize];
        ::MultiByteToWideChar(cpDoc, 0, s.c_str(), s.size(), vwcText, lengthUTF16);

        // Change case
        int charsConverted = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags,
            vwcText, lengthUTF16, NULL, 0);
        // Full mapping may produce up to 3 characters per input character
        wchar_t vwcConverted[shortSize*3];
        ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags, vwcText, lengthUTF16,
            vwcConverted, charsConverted);

        // Change back to document encoding
        unsigned int lengthConverted = ::WideCharToMultiByte(cpDoc, 0,
            vwcConverted, charsConverted,
            NULL, 0, NULL, 0);
        // Each UTF-16 code unit may need up to 3 bytes in UTF-8
        char vcConverted[shortSize * 3 * 3];
        ::WideCharToMultiByte(cpDoc, 0,
            vwcConverted, charsConverted,
            vcConverted, lengthConverted, NULL, 0);

        return std::string(vcConverted, lengthConverted);
    }
}

void ScintillaWin::Copy() {
    //Platform::DebugPrintf("Copy\n");
    if (!sel.Empty()) {
        SelectionText selectedText;
        CopySelectionRange(&selectedText);
        CopyToClipboard(selectedText);
    }
}

void ScintillaWin::CopyAllowLine() {
    SelectionText selectedText;
    CopySelectionRange(&selectedText, true);
    CopyToClipboard(selectedText);
}

bool ScintillaWin::CanPaste() {
    if (!Editor::CanPaste())
        return false;
    if (::IsClipboardFormatAvailable(CF_TEXT))
        return true;
    if (IsUnicodeMode())
        return ::IsClipboardFormatAvailable(CF_UNICODETEXT) != 0;
    return false;
}

class GlobalMemory {
    HGLOBAL hand;
public:
    void *ptr;
    GlobalMemory() : hand(0), ptr(0) {
    }
    GlobalMemory(HGLOBAL hand_) : hand(hand_), ptr(0) {
        if (hand) {
            ptr = ::GlobalLock(hand);
        }
    }
    ~GlobalMemory() {
        PLATFORM_ASSERT(!ptr);
    }
    void Allocate(size_t bytes) {
        hand = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, bytes);
        if (hand) {
            ptr = ::GlobalLock(hand);
        }
    }
    HGLOBAL Unlock() {
        PLATFORM_ASSERT(ptr);
        HGLOBAL handCopy = hand;
        ::GlobalUnlock(hand);
        ptr = 0;
        hand = 0;
        return handCopy;
    }
    void SetClip(UINT uFormat) {
        ::SetClipboardData(uFormat, Unlock());
    }
    operator bool() const {
        return ptr != 0;
    }
    SIZE_T Size() {
        return ::GlobalSize(hand);
    }
};

void ScintillaWin::InsertPasteText(const char *text, int len, SelectionPosition selStart, bool isRectangular, bool isLine) {
    if (isRectangular) {
        PasteRectangular(selStart, text, len);
    } else {
        char *convertedText = 0;
        if (convertPastes) {
            // Convert line endings of the paste into our local line-endings mode
            convertedText = Document::TransformLineEnds(&len, text, len, pdoc->eolMode);
            text = convertedText;
        }
        if (isLine) {
            int insertPos = pdoc->LineStart(pdoc->LineFromPosition(sel.MainCaret()));
            pdoc->InsertString(insertPos, text, len);
            // add the newline if necessary
            if ((len > 0) && (text[len-1] != '\n' && text[len-1] != '\r')) {
                const char *endline = StringFromEOLMode(pdoc->eolMode);
                pdoc->InsertString(insertPos + len, endline, strlen(endline));
                len += strlen(endline);
            }
            if (sel.MainCaret() == insertPos) {
                SetEmptySelection(sel.MainCaret() + len);
            }
        } else {
            InsertPaste(selStart, text, len);
        }
        delete []convertedText;
    }
}

void ScintillaWin::Paste() {
    if (!::OpenClipboard(MainHWND()))
        return;
    UndoGroup ug(pdoc);
    bool isLine = SelectionEmpty() && (::IsClipboardFormatAvailable(cfLineSelect) != 0);
    ClearSelection(multiPasteMode == SC_MULTIPASTE_EACH);
    SelectionPosition selStart = sel.IsRectangular() ?
        sel.Rectangular().Start() :
        sel.Range(sel.Main()).Start();
    bool isRectangular = ::IsClipboardFormatAvailable(cfColumnSelect) != 0;

    // Always use CF_UNICODETEXT if available
    GlobalMemory memUSelection(::GetClipboardData(CF_UNICODETEXT));
    if (memUSelection) {
        wchar_t *uptr = static_cast<wchar_t *>(memUSelection.ptr);
        if (uptr) {
            unsigned int len;
            char *putf;
            // Default Scintilla behaviour in Unicode mode
            if (IsUnicodeMode()) {
                unsigned int bytes = memUSelection.Size();
                len = UTF8Length(uptr, bytes / 2);
                putf = new char[len + 1];
                UTF8FromUTF16(uptr, bytes / 2, putf, len);
            } else {
                // CF_UNICODETEXT available, but not in Unicode mode
                // Convert from Unicode to current Scintilla code page
                UINT cpDest = CodePageOfDocument();
                len = ::WideCharToMultiByte(cpDest, 0, uptr, -1,
                                            NULL, 0, NULL, NULL) - 1; // subtract 0 terminator
                putf = new char[len + 1];
                ::WideCharToMultiByte(cpDest, 0, uptr, -1,
                                          putf, len + 1, NULL, NULL);
            }

            InsertPasteText(putf, len, selStart, isRectangular, isLine);
            delete []putf;
        }
        memUSelection.Unlock();
    } else {
        // CF_UNICODETEXT not available, paste ANSI text
        GlobalMemory memSelection(::GetClipboardData(CF_TEXT));
        if (memSelection) {
            char *ptr = static_cast<char *>(memSelection.ptr);
            if (ptr) {
                unsigned int bytes = memSelection.Size();
                unsigned int len = bytes;
                for (unsigned int i = 0; i < bytes; i++) {
                    if ((len == bytes) && (0 == ptr[i]))
                        len = i;
                }

                // In Unicode mode, convert clipboard text to UTF-8
                if (IsUnicodeMode()) {
                    wchar_t *uptr = new wchar_t[len+1];

                    unsigned int ulen = ::MultiByteToWideChar(CP_ACP, 0,
                                        ptr, len, uptr, len+1);

                    unsigned int mlen = UTF8Length(uptr, ulen);
                    char *putf = new char[mlen + 1];
                    if (putf) {
                        // CP_UTF8 not available on Windows 95, so use UTF8FromUTF16()
                        UTF8FromUTF16(uptr, ulen, putf, mlen);
                    }

                    delete []uptr;

                    if (putf) {
                        InsertPasteText(putf, mlen, selStart, isRectangular, isLine);
                        delete []putf;
                    }
                } else {
                    InsertPasteText(ptr, len, selStart, isRectangular, isLine);
                }
            }
            memSelection.Unlock();
        }
    }
    ::CloseClipboard();
    Redraw();
}

void ScintillaWin::CreateCallTipWindow(PRectangle) {
    if (!ct.wCallTip.Created()) {
        ct.wCallTip = ::CreateWindow(callClassName, TEXT("ACallTip"),
                         WS_POPUP, 100, 100, 150, 20,
                         MainHWND(), 0,
                         GetWindowInstance(MainHWND()),
                         this);
        ct.wDraw = ct.wCallTip;
    }
}

void ScintillaWin::AddToPopUp(const char *label, int cmd, bool enabled) {
    HMENU hmenuPopup = reinterpret_cast<HMENU>(popup.GetID());
    if (!label[0])
        ::AppendMenuA(hmenuPopup, MF_SEPARATOR, 0, "");
    else if (enabled)
        ::AppendMenuA(hmenuPopup, MF_STRING, cmd, label);
    else
        ::AppendMenuA(hmenuPopup, MF_STRING | MF_DISABLED | MF_GRAYED, cmd, label);
}

void ScintillaWin::ClaimSelection() {
    // Windows does not have a primary selection
}

/// Implement IUnknown

STDMETHODIMP_(ULONG)FormatEnumerator_AddRef(FormatEnumerator *fe);
STDMETHODIMP FormatEnumerator_QueryInterface(FormatEnumerator *fe, REFIID riid, PVOID *ppv) {
    //Platform::DebugPrintf("EFE QI");
    *ppv = NULL;
    if (riid == IID_IUnknown)
        *ppv = reinterpret_cast<IEnumFORMATETC *>(fe);
    if (riid == IID_IEnumFORMATETC)
        *ppv = reinterpret_cast<IEnumFORMATETC *>(fe);
    if (!*ppv)
        return E_NOINTERFACE;
    FormatEnumerator_AddRef(fe);
    return S_OK;
}
STDMETHODIMP_(ULONG)FormatEnumerator_AddRef(FormatEnumerator *fe) {
    return ++fe->ref;
}
STDMETHODIMP_(ULONG)FormatEnumerator_Release(FormatEnumerator *fe) {
    fe->ref--;
    if (fe->ref > 0)
        return fe->ref;
    delete fe;
    return 0;
}
/// Implement IEnumFORMATETC
STDMETHODIMP FormatEnumerator_Next(FormatEnumerator *fe, ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) {
    //Platform::DebugPrintf("EFE Next %d %d", fe->pos, celt);
    if (rgelt == NULL) return E_POINTER;
    // We only support one format, so this is simple.
    unsigned int putPos = 0;
    while ((fe->pos < fe->formatsLen) && (putPos < celt)) {
        rgelt->cfFormat = fe->formats[fe->pos];
        rgelt->ptd = 0;
        rgelt->dwAspect = DVASPECT_CONTENT;
        rgelt->lindex = -1;
        rgelt->tymed = TYMED_HGLOBAL;
        fe->pos++;
        putPos++;
    }
    if (pceltFetched)
        *pceltFetched = putPos;
    return putPos ? S_OK : S_FALSE;
}
STDMETHODIMP FormatEnumerator_Skip(FormatEnumerator *fe, ULONG celt) {
    fe->pos += celt;
    return S_OK;
}
STDMETHODIMP FormatEnumerator_Reset(FormatEnumerator *fe) {
    fe->pos = 0;
    return S_OK;
}
STDMETHODIMP FormatEnumerator_Clone(FormatEnumerator *fe, IEnumFORMATETC **ppenum) {
    FormatEnumerator *pfe;
    try {
        pfe = new FormatEnumerator(fe->pos, fe->formats, fe->formatsLen);
    } catch (...) {
        return E_OUTOFMEMORY;
    }
    return FormatEnumerator_QueryInterface(pfe, IID_IEnumFORMATETC,
                                           reinterpret_cast<void **>(ppenum));
}

static VFunction *vtFormatEnumerator[] = {
    (VFunction *)(FormatEnumerator_QueryInterface),
    (VFunction *)(FormatEnumerator_AddRef),
    (VFunction *)(FormatEnumerator_Release),
    (VFunction *)(FormatEnumerator_Next),
    (VFunction *)(FormatEnumerator_Skip),
    (VFunction *)(FormatEnumerator_Reset),
    (VFunction *)(FormatEnumerator_Clone)
};

FormatEnumerator::FormatEnumerator(int pos_, CLIPFORMAT formats_[], int formatsLen_) {
    vtbl = vtFormatEnumerator;
    ref = 0;   // First QI adds first reference...
    pos = pos_;
    formatsLen = formatsLen_;
    for (int i=0; i<formatsLen; i++)
        formats[i] = formats_[i];
}

/// Implement IUnknown
STDMETHODIMP DropSource_QueryInterface(DropSource *ds, REFIID riid, PVOID *ppv) {
    return ds->sci->QueryInterface(riid, ppv);
}
STDMETHODIMP_(ULONG)DropSource_AddRef(DropSource *ds) {
    return ds->sci->AddRef();
}
STDMETHODIMP_(ULONG)DropSource_Release(DropSource *ds) {
    return ds->sci->Release();
}

/// Implement IDropSource
STDMETHODIMP DropSource_QueryContinueDrag(DropSource *, BOOL fEsc, DWORD grfKeyState) {
    if (fEsc)
        return DRAGDROP_S_CANCEL;
    if (!(grfKeyState & MK_LBUTTON))
        return DRAGDROP_S_DROP;
    return S_OK;
}

STDMETHODIMP DropSource_GiveFeedback(DropSource *, DWORD) {
    return DRAGDROP_S_USEDEFAULTCURSORS;
}

static VFunction *vtDropSource[] = {
    (VFunction *)(DropSource_QueryInterface),
    (VFunction *)(DropSource_AddRef),
    (VFunction *)(DropSource_Release),
    (VFunction *)(DropSource_QueryContinueDrag),
    (VFunction *)(DropSource_GiveFeedback)
};

DropSource::DropSource() {
    vtbl = vtDropSource;
    sci = 0;
}

/// Implement IUnkown
STDMETHODIMP DataObject_QueryInterface(DataObject *pd, REFIID riid, PVOID *ppv) {
    //Platform::DebugPrintf("DO QI %x\n", pd);
    return pd->sci->QueryInterface(riid, ppv);
}
STDMETHODIMP_(ULONG)DataObject_AddRef(DataObject *pd) {
    return pd->sci->AddRef();
}
STDMETHODIMP_(ULONG)DataObject_Release(DataObject *pd) {
    return pd->sci->Release();
}
/// Implement IDataObject
STDMETHODIMP DataObject_GetData(DataObject *pd, FORMATETC *pFEIn, STGMEDIUM *pSTM) {
    return pd->sci->GetData(pFEIn, pSTM);
}

STDMETHODIMP DataObject_GetDataHere(DataObject *, FORMATETC *, STGMEDIUM *) {
    //Platform::DebugPrintf("DOB GetDataHere\n");
    return E_NOTIMPL;
}

STDMETHODIMP DataObject_QueryGetData(DataObject *pd, FORMATETC *pFE) {
    if (pd->sci->DragIsRectangularOK(pFE->cfFormat) &&
        pFE->ptd == 0 &&
        (pFE->dwAspect & DVASPECT_CONTENT) != 0 &&
        pFE->lindex == -1 &&
        (pFE->tymed & TYMED_HGLOBAL) != 0
    ) {
        return S_OK;
    }

    bool formatOK = (pFE->cfFormat == CF_TEXT) ||
        ((pFE->cfFormat == CF_UNICODETEXT) && pd->sci->IsUnicodeMode());
    if (!formatOK ||
        pFE->ptd != 0 ||
        (pFE->dwAspect & DVASPECT_CONTENT) == 0 ||
        pFE->lindex != -1 ||
        (pFE->tymed & TYMED_HGLOBAL) == 0
    ) {
        //Platform::DebugPrintf("DOB QueryGetData No %x\n",pFE->cfFormat);
        //return DATA_E_FORMATETC;
        return S_FALSE;
    }
    //Platform::DebugPrintf("DOB QueryGetData OK %x\n",pFE->cfFormat);
    return S_OK;
}

STDMETHODIMP DataObject_GetCanonicalFormatEtc(DataObject *pd, FORMATETC *, FORMATETC *pFEOut) {
    //Platform::DebugPrintf("DOB GetCanon\n");
    if (pd->sci->IsUnicodeMode())
        pFEOut->cfFormat = CF_UNICODETEXT;
    else
        pFEOut->cfFormat = CF_TEXT;
    pFEOut->ptd = 0;
    pFEOut->dwAspect = DVASPECT_CONTENT;
    pFEOut->lindex = -1;
    pFEOut->tymed = TYMED_HGLOBAL;
    return S_OK;
}

STDMETHODIMP DataObject_SetData(DataObject *, FORMATETC *, STGMEDIUM *, BOOL) {
    //Platform::DebugPrintf("DOB SetData\n");
    return E_FAIL;
}

STDMETHODIMP DataObject_EnumFormatEtc(DataObject *pd, DWORD dwDirection, IEnumFORMATETC **ppEnum) {
    try {
        //Platform::DebugPrintf("DOB EnumFormatEtc %d\n", dwDirection);
        if (dwDirection != DATADIR_GET) {
            *ppEnum = 0;
            return E_FAIL;
        }
        FormatEnumerator *pfe;
        if (pd->sci->IsUnicodeMode()) {
            CLIPFORMAT formats[] = {CF_UNICODETEXT, CF_TEXT};
            pfe = new FormatEnumerator(0, formats, 2);
        } else {
            CLIPFORMAT formats[] = {CF_TEXT};
            pfe = new FormatEnumerator(0, formats, 1);
        }
        return FormatEnumerator_QueryInterface(pfe, IID_IEnumFORMATETC,
                                               reinterpret_cast<void **>(ppEnum));
    } catch (std::bad_alloc &) {
        pd->sci->errorStatus = SC_STATUS_BADALLOC;
        return E_OUTOFMEMORY;
    } catch (...) {
        pd->sci->errorStatus = SC_STATUS_FAILURE;
        return E_FAIL;
    }
}

STDMETHODIMP DataObject_DAdvise(DataObject *, FORMATETC *, DWORD, IAdviseSink *, PDWORD) {
    //Platform::DebugPrintf("DOB DAdvise\n");
    return E_FAIL;
}

STDMETHODIMP DataObject_DUnadvise(DataObject *, DWORD) {
    //Platform::DebugPrintf("DOB DUnadvise\n");
    return E_FAIL;
}

STDMETHODIMP DataObject_EnumDAdvise(DataObject *, IEnumSTATDATA **) {
    //Platform::DebugPrintf("DOB EnumDAdvise\n");
    return E_FAIL;
}

static VFunction *vtDataObject[] = {
    (VFunction *)(DataObject_QueryInterface),
    (VFunction *)(DataObject_AddRef),
    (VFunction *)(DataObject_Release),
    (VFunction *)(DataObject_GetData),
    (VFunction *)(DataObject_GetDataHere),
    (VFunction *)(DataObject_QueryGetData),
    (VFunction *)(DataObject_GetCanonicalFormatEtc),
    (VFunction *)(DataObject_SetData),
    (VFunction *)(DataObject_EnumFormatEtc),
    (VFunction *)(DataObject_DAdvise),
    (VFunction *)(DataObject_DUnadvise),
    (VFunction *)(DataObject_EnumDAdvise)
};

DataObject::DataObject() {
    vtbl = vtDataObject;
    sci = 0;
}

/// Implement IUnknown
STDMETHODIMP DropTarget_QueryInterface(DropTarget *dt, REFIID riid, PVOID *ppv) {
    //Platform::DebugPrintf("DT QI %x\n", dt);
    return dt->sci->QueryInterface(riid, ppv);
}
STDMETHODIMP_(ULONG)DropTarget_AddRef(DropTarget *dt) {
    return dt->sci->AddRef();
}
STDMETHODIMP_(ULONG)DropTarget_Release(DropTarget *dt) {
    return dt->sci->Release();
}

/// Implement IDropTarget by forwarding to Scintilla
STDMETHODIMP DropTarget_DragEnter(DropTarget *dt, LPDATAOBJECT pIDataSource, DWORD grfKeyState,
                                  POINTL pt, PDWORD pdwEffect) {
    try {
        return dt->sci->DragEnter(pIDataSource, grfKeyState, pt, pdwEffect);
    } catch (...) {
        dt->sci->errorStatus = SC_STATUS_FAILURE;
    }
    return E_FAIL;
}
STDMETHODIMP DropTarget_DragOver(DropTarget *dt, DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) {
    try {
        return dt->sci->DragOver(grfKeyState, pt, pdwEffect);
    } catch (...) {
        dt->sci->errorStatus = SC_STATUS_FAILURE;
    }
    return E_FAIL;
}
STDMETHODIMP DropTarget_DragLeave(DropTarget *dt) {
    try {
        return dt->sci->DragLeave();
    } catch (...) {
        dt->sci->errorStatus = SC_STATUS_FAILURE;
    }
    return E_FAIL;
}
STDMETHODIMP DropTarget_Drop(DropTarget *dt, LPDATAOBJECT pIDataSource, DWORD grfKeyState,
                             POINTL pt, PDWORD pdwEffect) {
    try {
        return dt->sci->Drop(pIDataSource, grfKeyState, pt, pdwEffect);
    } catch (...) {
        dt->sci->errorStatus = SC_STATUS_FAILURE;
    }
    return E_FAIL;
}

static VFunction *vtDropTarget[] = {
    (VFunction *)(DropTarget_QueryInterface),
    (VFunction *)(DropTarget_AddRef),
    (VFunction *)(DropTarget_Release),
    (VFunction *)(DropTarget_DragEnter),
    (VFunction *)(DropTarget_DragOver),
    (VFunction *)(DropTarget_DragLeave),
    (VFunction *)(DropTarget_Drop)
};

DropTarget::DropTarget() {
    vtbl = vtDropTarget;
    sci = 0;
}

/**
 * DBCS: support Input Method Editor (IME).
 * Called when IME Window opened.
 */
void ScintillaWin::ImeStartComposition() {
#ifndef __DMC__
    // Digital Mars compiler does not include Imm library
    if (caret.active) {
        // Move IME Window to current caret position
        HIMC hIMC = ::ImmGetContext(MainHWND());
        Point pos = PointMainCaret();
        COMPOSITIONFORM CompForm;
        CompForm.dwStyle = CFS_POINT;
        CompForm.ptCurrentPos.x = pos.x;
        CompForm.ptCurrentPos.y = pos.y;

        ::ImmSetCompositionWindow(hIMC, &CompForm);

        // Set font of IME window to same as surrounded text.
        if (stylesValid) {
            // Since the style creation code has been made platform independent,
            // The logfont for the IME is recreated here.
            int styleHere = (pdoc->StyleAt(sel.MainCaret())) & 31;
            LOGFONTA lf = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ""};
            int sizeZoomed = vs.styles[styleHere].size + vs.zoomLevel;
            if (sizeZoomed <= 2)    // Hangs if sizeZoomed <= 1
                sizeZoomed = 2;
            AutoSurface surface(this);
            int deviceHeight = sizeZoomed;
            if (surface) {
                deviceHeight = (sizeZoomed * surface->LogPixelsY()) / 72;
            }
            // The negative is to allow for leading
            lf.lfHeight = -(abs(deviceHeight));
            lf.lfWeight = vs.styles[styleHere].bold ? FW_BOLD : FW_NORMAL;
            lf.lfItalic = static_cast<BYTE>(vs.styles[styleHere].italic ? 1 : 0);
            lf.lfCharSet = DEFAULT_CHARSET;
            lf.lfFaceName[0] = '\0';
            if (vs.styles[styleHere].fontName)
                strcpy(lf.lfFaceName, vs.styles[styleHere].fontName);

            ::ImmSetCompositionFontA(hIMC, &lf);
        }
        ::ImmReleaseContext(MainHWND(), hIMC);
        // Caret is displayed in IME window. So, caret in Scintilla is useless.
        DropCaret();
    }
#endif
}

/** Called when IME Window closed. */
void ScintillaWin::ImeEndComposition() {
    ShowCaretAtCurrentPosition();
}

void ScintillaWin::AddCharBytes(char b0, char b1) {

    int inputCodePage = InputCodePage();
    if (inputCodePage && IsUnicodeMode()) {
        char utfval[4] = "\0\0\0";
        char ansiChars[3];
        wchar_t wcs[2];
        if (b0) {   // Two bytes from IME
            ansiChars[0] = b0;
            ansiChars[1] = b1;
            ansiChars[2] = '\0';
            ::MultiByteToWideChar(inputCodePage, 0, ansiChars, 2, wcs, 1);
        } else {
            ansiChars[0] = b1;
            ansiChars[1] = '\0';
            ::MultiByteToWideChar(inputCodePage, 0, ansiChars, 1, wcs, 1);
        }
        unsigned int len = UTF8Length(wcs, 1);
        UTF8FromUTF16(wcs, 1, utfval, len);
        utfval[len] = '\0';
        AddCharUTF(utfval, len ? len : 1);
    } else if (b0) {
        char dbcsChars[3];
        dbcsChars[0] = b0;
        dbcsChars[1] = b1;
        dbcsChars[2] = '\0';
        AddCharUTF(dbcsChars, 2, true);
    } else {
        AddChar(b1);
    }
}

void ScintillaWin::GetIntelliMouseParameters() {
    // This retrieves the number of lines per scroll as configured inthe Mouse Properties sheet in Control Panel
    ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0);
}

void ScintillaWin::CopyToClipboard(const SelectionText &selectedText) {
    if (!::OpenClipboard(MainHWND()))
        return;
    ::EmptyClipboard();

    GlobalMemory uniText;

    // Default Scintilla behaviour in Unicode mode
    if (IsUnicodeMode()) {
        int uchars = UTF16Length(selectedText.s, selectedText.len);
        uniText.Allocate(2 * uchars);
        if (uniText) {
            UTF16FromUTF8(selectedText.s, selectedText.len, static_cast<wchar_t *>(uniText.ptr), uchars);
        }
    } else {
        // Not Unicode mode
        // Convert to Unicode using the current Scintilla code page
        UINT cpSrc = CodePageFromCharSet(
                    selectedText.characterSet, selectedText.codePage);
        int uLen = ::MultiByteToWideChar(cpSrc, 0, selectedText.s, selectedText.len, 0, 0);
        uniText.Allocate(2 * uLen);
        if (uniText) {
            ::MultiByteToWideChar(cpSrc, 0, selectedText.s, selectedText.len,
                static_cast<wchar_t *>(uniText.ptr), uLen);
        }
    }

    if (uniText) {
        if (!IsNT()) {
            // Copy ANSI text to clipboard on Windows 9x
            // Convert from Unicode text, so other ANSI programs can
            // paste the text
            // Windows NT, 2k, XP automatically generates CF_TEXT
            GlobalMemory ansiText;
            ansiText.Allocate(selectedText.len);
            if (ansiText) {
                ::WideCharToMultiByte(CP_ACP, 0, static_cast<wchar_t *>(uniText.ptr), -1,
                    static_cast<char *>(ansiText.ptr), selectedText.len, NULL, NULL);
                ansiText.SetClip(CF_TEXT);
            }
        }
        uniText.SetClip(CF_UNICODETEXT);
    } else {
        // There was a failure - try to copy at least ANSI text
        GlobalMemory ansiText;
        ansiText.Allocate(selectedText.len);
        if (ansiText) {
            memcpy(static_cast<char *>(ansiText.ptr), selectedText.s, selectedText.len);
            ansiText.SetClip(CF_TEXT);
        }
    }

    if (selectedText.rectangular) {
        ::SetClipboardData(cfColumnSelect, 0);
    }

    if (selectedText.lineCopy) {
        ::SetClipboardData(cfLineSelect, 0);
    }

    ::CloseClipboard();
}

void ScintillaWin::ScrollMessage(WPARAM wParam) {
    //DWORD dwStart = timeGetTime();
    //Platform::DebugPrintf("Scroll %x %d\n", wParam, lParam);

    SCROLLINFO sci;
    memset(&sci, 0, sizeof(sci));
    sci.cbSize = sizeof(sci);
    sci.fMask = SIF_ALL;

    GetScrollInfo(SB_VERT, &sci);

    //Platform::DebugPrintf("ScrollInfo %d mask=%x min=%d max=%d page=%d pos=%d track=%d\n", b,sci.fMask,
    //sci.nMin, sci.nMax, sci.nPage, sci.nPos, sci.nTrackPos);

    int topLineNew = topLine;
    switch (LoWord(wParam)) {
    case SB_LINEUP:
        topLineNew -= 1;
        break;
    case SB_LINEDOWN:
        topLineNew += 1;
        break;
    case SB_PAGEUP:
        topLineNew -= LinesToScroll(); break;
    case SB_PAGEDOWN: topLineNew += LinesToScroll(); break;
    case SB_TOP: topLineNew = 0; break;
    case SB_BOTTOM: topLineNew = MaxScrollPos(); break;
    case SB_THUMBPOSITION: topLineNew = sci.nTrackPos; break;
    case SB_THUMBTRACK: topLineNew = sci.nTrackPos; break;
    }
    ScrollTo(topLineNew);
}

void ScintillaWin::HorizontalScrollMessage(WPARAM wParam) {
    int xPos = xOffset;
    PRectangle rcText = GetTextRectangle();
    int pageWidth = rcText.Width() * 2 / 3;
    switch (LoWord(wParam)) {
    case SB_LINEUP:
        xPos -= 20;
        break;
    case SB_LINEDOWN:   // May move past the logical end
        xPos += 20;
        break;
    case SB_PAGEUP:
        xPos -= pageWidth;
        break;
    case SB_PAGEDOWN:
        xPos += pageWidth;
        if (xPos > scrollWidth - rcText.Width()) {  // Hit the end exactly
            xPos = scrollWidth - rcText.Width();
        }
        break;
    case SB_TOP:
        xPos = 0;
        break;
    case SB_BOTTOM:
        xPos = scrollWidth;
        break;
    case SB_THUMBPOSITION:
    case SB_THUMBTRACK: {
            // Do NOT use wParam, its 16 bit and not enough for very long lines. Its still possible to overflow the 32 bit but you have to try harder =]
            SCROLLINFO si;
            si.cbSize = sizeof(si);
            si.fMask = SIF_TRACKPOS;
            if (GetScrollInfo(SB_HORZ, &si)) {
                xPos = si.nTrackPos;
            }
        }
        break;
    }
    HorizontalScrollTo(xPos);
}

void ScintillaWin::RealizeWindowPalette(bool inBackGround) {
    RefreshStyleData();
    HDC hdc = ::GetDC(MainHWND());
    // Select a stock font to prevent warnings from BoundsChecker
    ::SelectObject(hdc, GetStockFont(DEFAULT_GUI_FONT));
    AutoSurface surfaceWindow(hdc, this);
    if (surfaceWindow) {
        int changes = surfaceWindow->SetPalette(&palette, inBackGround);
        if (changes > 0)
            Redraw();
        surfaceWindow->Release();
    }
    ::ReleaseDC(MainHWND(), hdc);
}

/**
 * Redraw all of text area.
 * This paint will not be abandoned.
 */
void ScintillaWin::FullPaint() {
    HDC hdc = ::GetDC(MainHWND());
    FullPaintDC(hdc);
    ::ReleaseDC(MainHWND(), hdc);
}

/**
 * Redraw all of text area on the specified DC.
 * This paint will not be abandoned.
 */
void ScintillaWin::FullPaintDC(HDC hdc) {
    paintState = painting;
    rcPaint = GetClientRectangle();
    paintingAllText = true;
    AutoSurface surfaceWindow(hdc, this);
    if (surfaceWindow) {
        Paint(surfaceWindow, rcPaint);
        surfaceWindow->Release();
    }
    paintState = notPainting;
}

static bool CompareDevCap(HDC hdc, HDC hOtherDC, int nIndex) {
    return ::GetDeviceCaps(hdc, nIndex) == ::GetDeviceCaps(hOtherDC, nIndex);
}

bool ScintillaWin::IsCompatibleDC(HDC hOtherDC) {
    HDC hdc = ::GetDC(MainHWND());
    bool isCompatible =
        CompareDevCap(hdc, hOtherDC, TECHNOLOGY) &&
        CompareDevCap(hdc, hOtherDC, LOGPIXELSY) &&
        CompareDevCap(hdc, hOtherDC, LOGPIXELSX) &&
        CompareDevCap(hdc, hOtherDC, BITSPIXEL) &&
        CompareDevCap(hdc, hOtherDC, PLANES);
    ::ReleaseDC(MainHWND(), hdc);
    return isCompatible;
}

DWORD ScintillaWin::EffectFromState(DWORD grfKeyState) {
    // These are the Wordpad semantics.
    DWORD dwEffect;
    if (inDragDrop == ddDragging)   // Internal defaults to move
        dwEffect = DROPEFFECT_MOVE;
    else
        dwEffect = DROPEFFECT_COPY;
    if (grfKeyState & MK_ALT)
        dwEffect = DROPEFFECT_MOVE;
    if (grfKeyState & MK_CONTROL)
        dwEffect = DROPEFFECT_COPY;
    return dwEffect;
}

/// Implement IUnknown
STDMETHODIMP ScintillaWin::QueryInterface(REFIID riid, PVOID *ppv) {
    *ppv = NULL;
    if (riid == IID_IUnknown)
        *ppv = reinterpret_cast<IDropTarget *>(&dt);
    if (riid == IID_IDropSource)
        *ppv = reinterpret_cast<IDropSource *>(&ds);
    if (riid == IID_IDropTarget)
        *ppv = reinterpret_cast<IDropTarget *>(&dt);
    if (riid == IID_IDataObject)
        *ppv = reinterpret_cast<IDataObject *>(&dob);
    if (!*ppv)
        return E_NOINTERFACE;
    return S_OK;
}

STDMETHODIMP_(ULONG) ScintillaWin::AddRef() {
    return 1;
}

STDMETHODIMP_(ULONG) ScintillaWin::Release() {
    return 1;
}

/// Implement IDropTarget
STDMETHODIMP ScintillaWin::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
                                     POINTL, PDWORD pdwEffect) {
    if (pIDataSource == NULL)
        return E_POINTER;
    FORMATETC fmtu = {CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    HRESULT hrHasUText = pIDataSource->QueryGetData(&fmtu);
    hasOKText = (hrHasUText == S_OK);
    if (!hasOKText) {
        FORMATETC fmte = {CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
        HRESULT hrHasText = pIDataSource->QueryGetData(&fmte);
        hasOKText = (hrHasText == S_OK);
    }
    if (!hasOKText) {
        *pdwEffect = DROPEFFECT_NONE;
        return S_OK;
    }

    *pdwEffect = EffectFromState(grfKeyState);
    return S_OK;
}

STDMETHODIMP ScintillaWin::DragOver(DWORD grfKeyState, POINTL pt, PDWORD pdwEffect) {
    try {
        if (!hasOKText || pdoc->IsReadOnly()) {
            *pdwEffect = DROPEFFECT_NONE;
            return S_OK;
        }

        *pdwEffect = EffectFromState(grfKeyState);

        // Update the cursor.
        POINT rpt = {pt.x, pt.y};
        ::ScreenToClient(MainHWND(), &rpt);
        SetDragPosition(SPositionFromLocation(Point(rpt.x, rpt.y), false, false, UserVirtualSpace()));

        return S_OK;
    } catch (...) {
        errorStatus = SC_STATUS_FAILURE;
    }
    return E_FAIL;
}

STDMETHODIMP ScintillaWin::DragLeave() {
    try {
        SetDragPosition(SelectionPosition(invalidPosition));
        return S_OK;
    } catch (...) {
        errorStatus = SC_STATUS_FAILURE;
    }
    return E_FAIL;
}

STDMETHODIMP ScintillaWin::Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
                                POINTL pt, PDWORD pdwEffect) {
    try {
        *pdwEffect = EffectFromState(grfKeyState);

        if (pIDataSource == NULL)
            return E_POINTER;

        SetDragPosition(SelectionPosition(invalidPosition));

        STGMEDIUM medium = {0, {0}, 0};

        char *data = 0;
        bool dataAllocated = false;

        FORMATETC fmtu = {CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
        HRESULT hr = pIDataSource->GetData(&fmtu, &medium);
        if (SUCCEEDED(hr) && medium.hGlobal) {
            wchar_t *udata = static_cast<wchar_t *>(::GlobalLock(medium.hGlobal));
            if (IsUnicodeMode()) {
                int tlen = ::GlobalSize(medium.hGlobal);
                // Convert UTF-16 to UTF-8
                int dataLen = UTF8Length(udata, tlen/2);
                data = new char[dataLen+1];
                UTF8FromUTF16(udata, tlen/2, data, dataLen);
                dataAllocated = true;
            } else {
                // Convert UTF-16 to ANSI
                //
                // Default Scintilla behavior in Unicode mode
                // CF_UNICODETEXT available, but not in Unicode mode
                // Convert from Unicode to current Scintilla code page
                UINT cpDest = CodePageOfDocument();
                int tlen = ::WideCharToMultiByte(cpDest, 0, udata, -1,
                    NULL, 0, NULL, NULL) - 1; // subtract 0 terminator
                data = new char[tlen + 1];
                memset(data, 0, (tlen+1));
                ::WideCharToMultiByte(cpDest, 0, udata, -1,
                        data, tlen + 1, NULL, NULL);
                dataAllocated = true;
            }
        }

        if (!data) {
            FORMATETC fmte = {CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
            hr = pIDataSource->GetData(&fmte, &medium);
            if (SUCCEEDED(hr) && medium.hGlobal) {
                data = static_cast<char *>(::GlobalLock(medium.hGlobal));
            }
        }

        if (data && convertPastes) {
            // Convert line endings of the drop into our local line-endings mode
            int len = strlen(data);
            char *convertedText = Document::TransformLineEnds(&len, data, len, pdoc->eolMode);
            if (dataAllocated)
                delete []data;
            data = convertedText;
            dataAllocated = true;
        }

        if (!data) {
            //Platform::DebugPrintf("Bad data format: 0x%x\n", hres);
            return hr;
        }

        FORMATETC fmtr = {cfColumnSelect, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
        HRESULT hrRectangular = pIDataSource->QueryGetData(&fmtr);

        POINT rpt = {pt.x, pt.y};
        ::ScreenToClient(MainHWND(), &rpt);
        SelectionPosition movePos = SPositionFromLocation(Point(rpt.x, rpt.y), false, false, UserVirtualSpace());

        DropAt(movePos, data, *pdwEffect == DROPEFFECT_MOVE, hrRectangular == S_OK);

        ::GlobalUnlock(medium.hGlobal);

        // Free data
        if (medium.pUnkForRelease != NULL)
            medium.pUnkForRelease->Release();
        else
            ::GlobalFree(medium.hGlobal);

        if (dataAllocated)
            delete []data;

        return S_OK;
    } catch (...) {
        errorStatus = SC_STATUS_FAILURE;
    }
    return E_FAIL;
}

/// Implement important part of IDataObject
STDMETHODIMP ScintillaWin::GetData(FORMATETC *pFEIn, STGMEDIUM *pSTM) {
    bool formatOK = (pFEIn->cfFormat == CF_TEXT) ||
        ((pFEIn->cfFormat == CF_UNICODETEXT) && IsUnicodeMode());
    if (!formatOK ||
        pFEIn->ptd != 0 ||
        (pFEIn->dwAspect & DVASPECT_CONTENT) == 0 ||
        pFEIn->lindex != -1 ||
        (pFEIn->tymed & TYMED_HGLOBAL) == 0
    ) {
        //Platform::DebugPrintf("DOB GetData No %d %x %x fmt=%x\n", lenDrag, pFEIn, pSTM, pFEIn->cfFormat);
        return DATA_E_FORMATETC;
    }
    pSTM->tymed = TYMED_HGLOBAL;
    //Platform::DebugPrintf("DOB GetData OK %d %x %x\n", lenDrag, pFEIn, pSTM);

    GlobalMemory text;
    if (pFEIn->cfFormat == CF_UNICODETEXT) {
        int uchars = UTF16Length(drag.s, drag.len);
        text.Allocate(2 * uchars);
        if (text) {
            UTF16FromUTF8(drag.s, drag.len, static_cast<wchar_t *>(text.ptr), uchars);
        }
    } else {
        text.Allocate(drag.len);
        if (text) {
            memcpy(static_cast<char *>(text.ptr), drag.s, drag.len);
        }
    }
    pSTM->hGlobal = text ? text.Unlock() : 0;
    pSTM->pUnkForRelease = 0;
    return S_OK;
}

bool ScintillaWin::Register(HINSTANCE hInstance_) {

    hInstance = hInstance_;
    bool result;

    // Register the Scintilla class
    if (IsNT()) {

        // Register Scintilla as a wide character window
        WNDCLASSEXW wndclass;
        wndclass.cbSize = sizeof(wndclass);
        wndclass.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = ScintillaWin::SWndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = sizeof(ScintillaWin *);
        wndclass.hInstance = hInstance;
        wndclass.hIcon = NULL;
        wndclass.hCursor = NULL;
        wndclass.hbrBackground = NULL;
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = L"Scintilla";
        wndclass.hIconSm = 0;
        result = ::RegisterClassExW(&wndclass) != 0;
    } else {

        // Register Scintilla as a normal character window
        WNDCLASSEX wndclass;
        wndclass.cbSize = sizeof(wndclass);
        wndclass.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = ScintillaWin::SWndProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = sizeof(ScintillaWin *);
        wndclass.hInstance = hInstance;
        wndclass.hIcon = NULL;
        wndclass.hCursor = NULL;
        wndclass.hbrBackground = NULL;
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = scintillaClassName;
        wndclass.hIconSm = 0;
        result = ::RegisterClassEx(&wndclass) != 0;
    }

    if (result) {
        // Register the CallTip class
        WNDCLASSEX wndclassc;
        wndclassc.cbSize = sizeof(wndclassc);
        wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
        wndclassc.cbClsExtra = 0;
        wndclassc.cbWndExtra = sizeof(ScintillaWin *);
        wndclassc.hInstance = hInstance;
        wndclassc.hIcon = NULL;
        wndclassc.hbrBackground = NULL;
        wndclassc.lpszMenuName = NULL;
        wndclassc.lpfnWndProc = ScintillaWin::CTWndProc;
        wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
        wndclassc.lpszClassName = callClassName;
        wndclassc.hIconSm = 0;

        result = ::RegisterClassEx(&wndclassc) != 0;
    }

    return result;
}

bool ScintillaWin::Unregister() {
    bool result = ::UnregisterClass(scintillaClassName, hInstance) != 0;
    if (::UnregisterClass(callClassName, hInstance) == 0)
        result = false;
    return result;
}

bool ScintillaWin::HasCaretSizeChanged() {
    if (
        ( (0 != vs.caretWidth) && (sysCaretWidth != vs.caretWidth) )
        || ((0 != vs.lineHeight) && (sysCaretHeight != vs.lineHeight))
        ) {
        return true;
    }
    return false;
}

BOOL ScintillaWin::CreateSystemCaret() {
    sysCaretWidth = vs.caretWidth;
    if (0 == sysCaretWidth) {
        sysCaretWidth = 1;
    }
    sysCaretHeight = vs.lineHeight;
    int bitmapSize = (((sysCaretWidth + 15) & ~15) >> 3) *
        sysCaretHeight;
    char *bits = new char[bitmapSize];
    memset(bits, 0, bitmapSize);
    sysCaretBitmap = ::CreateBitmap(sysCaretWidth, sysCaretHeight, 1,
        1, reinterpret_cast<BYTE *>(bits));
    delete []bits;
    BOOL retval = ::CreateCaret(
        MainHWND(), sysCaretBitmap,
        sysCaretWidth, sysCaretHeight);
    ::ShowCaret(MainHWND());
    return retval;
}

BOOL ScintillaWin::DestroySystemCaret() {
    ::HideCaret(MainHWND());
    BOOL retval = ::DestroyCaret();
    if (sysCaretBitmap) {
        ::DeleteObject(sysCaretBitmap);
        sysCaretBitmap = 0;
    }
    return retval;
}

// Take care of 32/64 bit pointers
#ifdef GetWindowLongPtr
static void *PointerFromWindow(HWND hWnd) {
    return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
}
static void SetWindowPointer(HWND hWnd, void *ptr) {
    ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
}
#else
static void *PointerFromWindow(HWND hWnd) {
    return reinterpret_cast<void *>(::GetWindowLong(hWnd, 0));
}
static void SetWindowPointer(HWND hWnd, void *ptr) {
    ::SetWindowLong(hWnd, 0, reinterpret_cast<LONG>(ptr));
}
#endif

sptr_t PASCAL ScintillaWin::CTWndProc(
    HWND hWnd, UINT iMessage, WPARAM wParam, sptr_t lParam) {
    // Find C++ object associated with window.
    ScintillaWin *sciThis = reinterpret_cast<ScintillaWin *>(PointerFromWindow(hWnd));
    try {
        // ctp will be zero if WM_CREATE not seen yet
        if (sciThis == 0) {
            if (iMessage == WM_CREATE) {
                // Associate CallTip object with window
                CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
                SetWindowPointer(hWnd, pCreate->lpCreateParams);
                return 0;
            } else {
                return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
            }
        } else {
            if (iMessage == WM_NCDESTROY) {
                ::SetWindowLong(hWnd, 0, 0);
                return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
            } else if (iMessage == WM_PAINT) {
                PAINTSTRUCT ps;
                ::BeginPaint(hWnd, &ps);
                Surface *surfaceWindow = Surface::Allocate();
                if (surfaceWindow) {
                    surfaceWindow->Init(ps.hdc, hWnd);
                    surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == sciThis->ct.codePage);
                    surfaceWindow->SetDBCSMode(sciThis->ct.codePage);
                    sciThis->ct.PaintCT(surfaceWindow);
                    surfaceWindow->Release();
                    delete surfaceWindow;
                }
                ::EndPaint(hWnd, &ps);
                return 0;
            } else if ((iMessage == WM_NCLBUTTONDOWN) || (iMessage == WM_NCLBUTTONDBLCLK)) {
                POINT pt;
                pt.x = static_cast<short>(LOWORD(lParam));
                pt.y = static_cast<short>(HIWORD(lParam));
                ScreenToClient(hWnd, &pt);
                sciThis->ct.MouseClick(Point(pt.x, pt.y));
                sciThis->CallTipClick();
                return 0;
            } else if (iMessage == WM_LBUTTONDOWN) {
                // This does not fire due to the hit test code
                sciThis->ct.MouseClick(Point::FromLong(lParam));
                sciThis->CallTipClick();
                return 0;
            } else if (iMessage == WM_SETCURSOR) {
                ::SetCursor(::LoadCursor(NULL, IDC_ARROW));
                return 0;
            } else if (iMessage == WM_NCHITTEST) {
                return HTCAPTION;
            } else {
                return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
            }
        }
    } catch (...) {
        sciThis->errorStatus = SC_STATUS_FAILURE;
    }
    return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
}

sptr_t ScintillaWin::DirectFunction(
    ScintillaWin *sci, UINT iMessage, uptr_t wParam, sptr_t lParam) {
    PLATFORM_ASSERT(::GetCurrentThreadId() == ::GetWindowThreadProcessId(sci->MainHWND(), NULL));
    return sci->WndProc(iMessage, wParam, lParam);
}

extern "C"
#ifndef STATIC_BUILD
__declspec(dllexport)
#endif
sptr_t __stdcall Scintilla_DirectFunction(
    ScintillaWin *sci, UINT iMessage, uptr_t wParam, sptr_t lParam) {
    return sci->WndProc(iMessage, wParam, lParam);
}

sptr_t PASCAL ScintillaWin::SWndProc(
    HWND hWnd, UINT iMessage, WPARAM wParam, sptr_t lParam) {
    //Platform::DebugPrintf("S W:%x M:%x WP:%x L:%x\n", hWnd, iMessage, wParam, lParam);

    // Find C++ object associated with window.
    ScintillaWin *sci = reinterpret_cast<ScintillaWin *>(PointerFromWindow(hWnd));
    // sci will be zero if WM_CREATE not seen yet
    if (sci == 0) {
        try {
            if (iMessage == WM_CREATE) {
                // Create C++ object associated with window
                sci = new ScintillaWin(hWnd);
                SetWindowPointer(hWnd, sci);
                return sci->WndProc(iMessage, wParam, lParam);
            }
        } catch (...) {
        }
        return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
    } else {
        if (iMessage == WM_NCDESTROY) {
            try {
                sci->Finalise();
                delete sci;
            } catch (...) {
            }
            ::SetWindowLong(hWnd, 0, 0);
            return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
        } else {
            return sci->WndProc(iMessage, wParam, lParam);
        }
    }
}

// This function is externally visible so it can be called from container when building statically.
// Must be called once only.
int Scintilla_RegisterClasses(void *hInstance) {
    Platform_Initialise(hInstance);
    bool result = ScintillaWin::Register(reinterpret_cast<HINSTANCE>(hInstance));
#ifdef SCI_LEXER
    Scintilla_LinkLexers();
#endif
    return result;
}

// This function is externally visible so it can be called from container when building statically.
int Scintilla_ReleaseResources() {
    bool result = ScintillaWin::Unregister();
    Platform_Finalise();
    return result;
}

#ifndef STATIC_BUILD
extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID) {
    //Platform::DebugPrintf("Scintilla::DllMain %d %d\n", hInstance, dwReason);
    if (dwReason == DLL_PROCESS_ATTACH) {
        if (!Scintilla_RegisterClasses(hInstance))
            return FALSE;
    } else if (dwReason == DLL_PROCESS_DETACH) {
        Scintilla_ReleaseResources();
    }
    return TRUE;
}
#endif
